package researchgrants.parts.GrantRequest;

import java.awt.Window;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import researchgrants.parts.GrantRequest.agency.AgencyContact;
import researchgrants.parts.GrantRequest.agency.LoggedDataPanelsAgencyContact;
import researchgrants.parts.GrantRequest.grantOfficer.GrantOfficer;
import researchgrants.parts.GrantRequest.perennialStipends.PerennialStipends;
import researchgrants.parts.GrantRequest.statusChanges.GrantRequestStatusChange;
import researchgrants.parts.GrantRequest.statusChanges.GrantRequestStatusChangeList;
import researchgrants.parts.LoggedData.HistoricallyLoggedData;
import researchgrants.parts.db.Db;
import researchgrants.parts.GrantRequest.expensePurpose.ExpensePurpose;
import researchgrants.parts.GrantRequest.expensePurpose.LoggedDataPanelsListExpensePurpose;
import researchgrants.parts.GrantRequest.grantOfficer.LoggedDataPanelsListGrantOfficer;
import researchgrants.parts.GrantRequest.nsfcode.LoggedDataPanelsTreeNsfCode;
import researchgrants.parts.GrantRequest.nsfcode.NsfCode;
import researchgrants.parts.GrantRequest.perennialStipends.PanelsPerennialStipends;
import researchgrants.parts.LoggedData.LogableDate;
import researchgrants.parts.LoggedData.LogableString;
import researchgrants.parts.LoggedData.panels.LoggedDataPanelsDate;
import researchgrants.parts.LoggedData.panels.LoggedDataPanelsText;
import researchgrants.parts.LoggedData.panels.LoggedDataPanelsTextArea;
import researchgrants.parts.pi.LoggedDataPanelsPis;
import researchgrants.parts.pi.PisList;

/**
 * Holds all the data of a the top-level <code>GrantRequest</code> object. It holds all the logic of manipulating GrantRequest.
 * <p>
 * It allows users of this class to retrieve the different HistoricallyLoggedData fields and their current values.
 */
public class GrantRequest {

    /**
     * retreives an existing grant request
     * @param id the id of the grant request to retreive
     * @return the grant request whose id was supplied
     */
    static GrantRequest getById(int id) {
        return (new GrantRequest(id));
    }

    /**
     * opens a window to input the user for a new grant request
     * @param parent parent window to relate to when inputing a new grant request
     * @return the newly created grant request
     */
    static GrantRequest inputNew(Window parent) {
        // initializes an unbound Grant Request View
        GrantRequestView viewNew = new GrantRequestView(parent);
        GrantRequest newGrantRequest=viewNew.getGrantRequest();
        return (newGrantRequest);
    }

    /**
     * Creates a new grant request from its supplied value. Used internally from <code>GrantRequestView</code> when issued by calling inputNew
     * @param titlePanels                       the <code>Panels</code> of the title <code>String</code>
     * @param pisPanels                         the <code>Panels</code> of the list of <code>Pi</code>
     * @param firstContactPanels                the <code>Panels</code> of the first contact <code>Date</code>
     * @param deadlinePanels                    the <code>Panels</code> of the deadline <code>Date</code>
     * @param expensePurposePanels              the <code>Panels</code> of the <code>ExpensePurpose</code>
     * @param nsfCodePanels                     the <code>Panels</code> of the <code>NsfCode</code>
     * @param grantOfficerPanels                the <code>Panels</code> of the <code>GrantOfficer</code>
     * @param notesPanels                       the <code>Panels</code> of the notes <code>String</code>
     * @param agencyContactPanels               the <code>Panels</code> of the <code>AgencyContact</code>
     * @param expectedPerennialStipendsPanels   the <code>Panels</code> of the expected <code>PerennialStipends</code>
     * @return      the newly created grant request
     */
    static GrantRequest createNewFromFields(LoggedDataPanelsText titlePanels, LoggedDataPanelsPis pisPanels, LoggedDataPanelsDate firstContactPanels, LoggedDataPanelsDate deadlinePanels, LoggedDataPanelsListExpensePurpose expensePurposePanels, LoggedDataPanelsTreeNsfCode nsfCodePanels, LoggedDataPanelsListGrantOfficer grantOfficerPanels, LoggedDataPanelsTextArea notesPanels, LoggedDataPanelsAgencyContact agencyContactPanels, PanelsPerennialStipends expectedPerennialStipendsPanels) {
        GrantRequest res=null;
        try {
            Connection conn=Db.openDbConnection();
            PreparedStatement createGrantRequestStatement = Db.createPreparedStatement(conn, "INSERT INTO tblGrantRequests (TitleRef, PIsRef, FirstContactRef, DeadlineRef, ExpensePurposeRef, NsfCodeRef, GrantOfficerRef, NotesRef, AgencyContactRef, ExpectedPerennialStipendsID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

            
            ////////////////////////////////////
            // Save all different data pieces
            ////////////////////////////////////
            titlePanels.saveData();
            pisPanels.saveData();
            firstContactPanels.saveData();
            deadlinePanels.saveData();
            expensePurposePanels.saveData();
            nsfCodePanels.saveData();
            grantOfficerPanels.saveData();
            notesPanels.saveData();
            agencyContactPanels.saveData();
            expectedPerennialStipendsPanels.saveData();
        
            createGrantRequestStatement.setInt(1, titlePanels.getData().getId());
            createGrantRequestStatement.setInt(2, pisPanels.getData().getId());
            createGrantRequestStatement.setInt(3, firstContactPanels.getData().getId());
            createGrantRequestStatement.setInt(4, deadlinePanels.getData().getId());
            createGrantRequestStatement.setInt(5, expensePurposePanels.getData().getId());
            createGrantRequestStatement.setInt(6, nsfCodePanels.getData().getId());
            createGrantRequestStatement.setInt(7, grantOfficerPanels.getData().getId());
            createGrantRequestStatement.setInt(8, notesPanels.getData().getId());
            createGrantRequestStatement.setInt(9, agencyContactPanels.getData().getId());
            createGrantRequestStatement.setInt(10, expectedPerennialStipendsPanels.getPerennialStipends().getId());
                    
            /////////////////////////////////////////
            // Saves the GrantRequest itself
            /////////////////////////////////////////
            createGrantRequestStatement.executeUpdate();

            int newGrantRequestId = Db.getLastIdentity();
            createGrantRequestStatement.close();
            Db.closeDbConnection(conn);
            
            // saves the newly created grant request
            res=GrantRequest.getById(newGrantRequestId);
            
            //setVisible(false);
            
        } catch (SQLException ex) {
            Logger.getLogger(GrantRequestView.class.getName()).log(Level.SEVERE, null, ex);
        }
        return(res);
    }
    

    /**
     * the identification field of the grant request
     */
    private int id;
    
    // accompanying data
    private HistoricallyLoggedData title;
    private HistoricallyLoggedData deadline;
    private HistoricallyLoggedData firstContact;
    private HistoricallyLoggedData pis;
    private HistoricallyLoggedData expensePurpose;
    private HistoricallyLoggedData grantOfficer;
    private HistoricallyLoggedData nsfCode;
    private HistoricallyLoggedData notes;
    private HistoricallyLoggedData agencyContact;
    private PerennialStipends expectedPerennialStipends;
    
    /**
     * the accompanying status changes for this grant request
     */
    GrantRequestStatusChangeList statusChangeList;
    
    /**
     * An outside method for refreshing a GrantRequest. Gets called when an inner value changes implicitly (through LoggedDataManagers and LoggedDataPanels, and StatusChange updates).
     */
    public void refreshData() {
        syncDataFromDb();
    }

    /**
     * populate the fields from the database
     */
    private void syncDataFromDb() {
        try {
            Connection conn=Db.openDbConnection();
            PreparedStatement stat = Db.createPreparedStatement(conn, "SELECT TitleRef, DeadlineRef, FirstContactRef, PisRef, ExpensePurposeRef, GrantOfficerRef, NsfCodeRef, NotesRef, AgencyContactRef, ExpectedPerennialStipendsID FROM TblGrantRequests Where GrantRequestID=?");
            stat.setInt(1, id);
            stat.executeQuery();
            ResultSet rs=stat.getResultSet();
            Db.moveToFirstLine(rs);

            title=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 1));
            deadline=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 2));
            firstContact=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 3));
            pis=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 4));
            expensePurpose=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 5));
            grantOfficer=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 6));
            nsfCode=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 7));
            notes=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 8));
            agencyContact=HistoricallyLoggedData.getById((Integer) Db.getColumn(rs, 9));
            expectedPerennialStipends=PerennialStipends.getByID((Integer) Db.getColumn(rs, 10));
            
            rs.close();
            stat.close();
            Db.closeDbConnection(conn);
            
        } catch (SQLException ex) {
            Logger.getLogger(HistoricallyLoggedData.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Problem with Data: " + ex.getLocalizedMessage());
        }
        
        statusChangeList=GrantRequestStatusChangeList.getGrantRequestStatusChangeSetForGrantRequest(id);
    }

    /**
     * late initialization
     * @param grantRequestId    the id of the <code>GrantRequest</code> to load
     */
    private GrantRequest(int grantRequestId) {
        this.id = grantRequestId;
//        syncDataFromDb();
    }

    /**
     * displays the grant request for the user and allows editing the content.
     * @param parent  the parent window to relate to when displaying the grant request
     */
    void show(Window parent) {
        new GrantRequestView(parent, this);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final GrantRequest other = (GrantRequest) obj;
        return(other.id==id);
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 97 * hash + this.id;
        return hash;
    }
    
    /**
     * Returns the id of the <code>GrantRequest</code>
     * @return  the id of the <code>GrantRequest</code>
     */
    public int getId() {
        return(id);
    }
    
    /**
     * returns the last <code>GrantRequestStatusChange</code> for this <code>GrantRequest</code> - the current status
     * @return the last <code>GrantRequestStatusChange</code> 
     */
    public GrantRequestStatusChange getLastStatusChange() {
        syncDataFromDb();
        Iterator<GrantRequestStatusChange> iteratorStatusChangeList = statusChangeList.iterator();
        GrantRequestStatusChange grantRequestStatusChange=null;
        while (iteratorStatusChangeList.hasNext()) {
            grantRequestStatusChange = iteratorStatusChangeList.next();
        }
        return(grantRequestStatusChange);
    }
    
    
    /*
     * allows retrieval of the values of all the fields
     */
    
    /**
     * allows retrieval of the value of a field
     * @return  the currently saved title <code>String</code>
     */
    public LogableString getCurrentTitle() {
        syncDataFromDb();
        return(((LogableString)(title.value())));
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved deadline <code>Date</code>
     */
    public LogableDate getCurrentDeadline() {
        syncDataFromDb();
        return((LogableDate)deadline.value());
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved first contact date
     */
    public LogableDate getCurrentFirstContact() {
        syncDataFromDb();
        return((LogableDate)firstContact.value());
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved list of <code>Pi</code>s
     */
    public PisList getCurrentPis() {
        syncDataFromDb();
        return(PisList.fromLoggedData(pis.value()));
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved <code>ExpensePurpose</code>
     */
    public ExpensePurpose getCurrentExpensePurpose() {
        syncDataFromDb();
        final String value = expensePurpose.value();
        if (value.equals("")) {
            return(null);
        } else {
            return(ExpensePurpose.fromLoggedData(value));
        }
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved <code>GrantOfficer</code>
     */
    public GrantOfficer getCurrentGrantOfficer() {
        syncDataFromDb();
        final String value = grantOfficer.value();
        if (value.equals("")) {
            return(null);
        } else {
            return(GrantOfficer.fromLoggedData(value));
        }
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved expected <code>PerennialStipends</code>
     */
    public PerennialStipends getExpectedPerennialStipends() {
        syncDataFromDb();
        return(expectedPerennialStipends);
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved <code>NsfCode</code>
     */
    public NsfCode getCurrentNsfCode() {
        syncDataFromDb();
        final String value = nsfCode.value();
        if (value.equals("")) {
            return(null);
        } else {
            return(NsfCode.fromLoggedData(value));
        }
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved notes
     */
    public String getCurrentNotes() {
        syncDataFromDb();
        return(notes.value());
    }
    
    /**
     * allows retrieval of the value of a field
     * @return the currently saved <code>AgencyContact</code>
     */
    public AgencyContact getCurrentAgencyContact() {
        syncDataFromDb();
        final String value = agencyContact.value();
        if (value.equals("")) {
            return(null);
        } else {
            return(AgencyContact.fromLoggedData(value));
        }
    }

    /*
     * allows retrieval of all raw different fields - to be used to populate <code>Panels</code> for editing and saving changes.
     */
    
    /**
     * allows retrieval of a field to be edited independently
     * @return the <code>HistoricallyLoggedData</code> of the <code>AgencyContact</code>
     */
    HistoricallyLoggedData getLoggedDataAgencyContact() {
        syncDataFromDb();
        return(agencyContact);
    }

    /**
     * allows retrieval of a field to be edited independently
     * @return the <code>HistoricallyLoggedData</code> of the deadline <code>Date</code>
     */
    HistoricallyLoggedData getLoggedDataDeadline() {
        syncDataFromDb();
        return(deadline);
    }

    HistoricallyLoggedData getLoggedDataExpensePurpose() {
        syncDataFromDb();
        return(expensePurpose);
    }

    HistoricallyLoggedData getLoggedDataFirstContact() {
        syncDataFromDb();
        return(firstContact);
    }

    HistoricallyLoggedData getLoggedDataGrantOfficer() {
        syncDataFromDb();
        return(grantOfficer);
    }

    HistoricallyLoggedData getLoggedDataNotes() {
        syncDataFromDb();
        return(notes);
    }

    HistoricallyLoggedData getLoggedDataNsfCode() {
        syncDataFromDb();
        return(nsfCode);
    }

    HistoricallyLoggedData getLoggedDataPis() {
        syncDataFromDb();
        return(pis);
    }

    HistoricallyLoggedData getLoggedDataTitle() {
        syncDataFromDb();
        return(title);
    }

}
